package org.mozhu.mboot.core.data.hibernate.type;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.TextType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.mozhu.mboot.core.json.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;

@SuppressWarnings({"rawtypes", "unchecked"})
public class JSONObjectUserType implements UserType, ParameterizedType, Serializable {

    private static final long serialVersionUID = -2801275099258595832L;
    private static final Logger logger = LoggerFactory.getLogger(JSONObjectUserType.class);

    public static final String OBJECT_TYPE = "objectType";
    private Class objectClass;

    @Override
    public int[] sqlTypes() {
        return new int[]{Types.VARCHAR};
    }

    @Override
    public Class returnedClass() {
        return Object.class;
    }

    @Override
    public boolean equals(Object o, Object o1) throws HibernateException {
        if (o == o1) {
            return true;
        }
        if (o == null || o == null) {
            return false;
        }

        return o.equals(o1);
    }

    @Override
    public int hashCode(Object o) throws HibernateException {
        return o.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        logger.debug("nullSafeGet owner[{}]", ToStringBuilder.reflectionToString(owner));
        String text = (String) TextType.INSTANCE.nullSafeGet(rs, names, session, owner);
        if (text == null) {
            return null;
        }

        try {
            return JSON.deserialize(new StringReader(text), objectClass);
        } catch (Exception e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.VARCHAR);
        } else {
            try {
                StringWriter writer = new StringWriter();
                JSON.serialize(writer, value);
                TextType.INSTANCE.nullSafeSet(st, writer.toString(), index, session);
            } catch (Exception e) {
                throw new HibernateException(e);
            }
        }
    }

    @Override
    public Object deepCopy(Object o) throws HibernateException {
        if (o == null) {
            return null;
        }
        try {
            StringWriter writer = new StringWriter();
            JSON.serialize(writer, o);
            return JSON.deserialize(new StringReader(writer.toString()));
        } catch (Exception e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        logger.debug("assemble owner[" + ToStringBuilder.reflectionToString(owner) + "]");
        return deepCopy(cached);
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) deepCopy(value);
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    @Override
    public void setParameterValues(Properties properties) {
        String objectType = properties.getProperty(OBJECT_TYPE);
        if (objectType == null) {
            throw new IllegalArgumentException("illegal params setting[" + properties + "]");
        }
        this.objectClass = getClassType(objectType);
    }

    private static Class<?> getClassType(String clazzType) {
        try {
            return Class.forName(clazzType);
        } catch (Exception e) {
            throw new IllegalStateException("Illegal class [" + clazzType + "]", e);
        }
    }

}
